package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	RESP_LOG          = 0x01
	RESP_FORMAT       = 0x01 << 1
	RESP_TRANSMIT     = 0x01 << 2
	RESP_TRANSMIT_ALL = 0x01 << 3
)

var (
	shtmlReg     = regexp.MustCompile("<!--#include[\\s\\r\\t]+file=['\"](.+?)['\"][\\s\\r\\t]* -->")
	httpClient   http.Client
	httpLogMutex sync.Mutex
)

//type HttpHandler struct {
//	sync.Mutex
//	server         *ServerBlock
//	regs           []*regexp.Regexp
//	remotes        []*url.URL
//	reverseProxies []*httputil.ReverseProxy
//	shtmlReg       *regexp.Regexp
//	httpClient     *http.Client
//}

func NewHttpHandler(server *ServerBlock) func(w http.ResponseWriter, r *http.Request) {
	regs := make([]*regexp.Regexp, len(server.Locations))
	//remotes := make([]*url.URL, len(server.Locations))
	reverseProxies := make([]*httputil.ReverseProxy, len(server.Locations))
	for i, value := range server.Locations {
		//ins.regs[i] = nil
		var reg *regexp.Regexp
		if value.Url[0] == '^' {
			reg = regexp.MustCompile(value.Url)
		} else {
			reg = regexp.MustCompile("^" + value.Url)
		}
		regs[i] = reg
		reverseProxies[i] = nil
		if len(value.ProxyPass) > 0 {
			remote, err := url.Parse(value.ProxyPass)
			if err != nil {
				continue
			}
			proxy := &httputil.ReverseProxy{
				Director:       requestFactory(remote, reg),
				ModifyResponse: responseFactory(value, remote, reg),
			}
			reverseProxies[i] = proxy
		}
		i++
	}
	//文件代理服务
	return func(w http.ResponseWriter, r *http.Request) {
		//从最后开始匹配，如果命中直接返回
		i := len(server.Locations)
		for i > 0 {
			i--
			location := server.Locations[i]
			reg := regs[i]
			if !reg.MatchString(r.URL.Path) {
				continue
			}
			if len(location.Alias) > 0 {
				path := regs[i].ReplaceAllString(r.URL.Path, "")
				//默认的情况，使用首页
				if path == "" && len(location.Index) > 0 {
					for idex := range location.Index {
						fp := location.Alias + location.Index[idex]
						if FileExists(fp) {
							_, name := filepath.Split(fp)
							path = name
							break
						}
					}
				}
				//http.NewFileTransport()
				if len(location.Ssi) > 0 {
					lastIdex := strings.LastIndex(path, location.Ssi)
					if lastIdex > -1 && lastIdex+len(location.Ssi) == len(path) {
						err := serveShtml(w, location.Alias, path)
						if err != nil {
							goto not_found
						}
					}
				}
				filePath := location.Alias + path
				f, err := os.Open(filePath)
				if err != nil {
					goto not_found
				}
				stat, err := f.Stat()
				if err != nil {
					goto not_found
				}

				//log.Println(stat.Name())
				//log.Println(filepath.Ext(stat.Name()))
				//log.Println(mime.TypeByExtension(filepath.Ext(stat.Name())))
				contentType := mime.TypeByExtension(filepath.Ext(stat.Name()))
				buf := pool.Get(1024)
				defer pool.Put(buf)
				if contentType == "" {
					_, err = io.ReadFull(f, buf[:512])
					if err != nil {
						goto not_found
					}
					contentType = http.DetectContentType(buf[:512])
					_, err = f.Seek(0, 0)
					if err != nil {
						goto not_found
					}
				}
				//转发
				canTransmit := 0
				canTransmit |= withTransmitParam(location, contentType)
				if (canTransmit & RESP_TRANSMIT_ALL) != 0 {
					request := *r
					header := request.Header
					go func() {
						//读取所有文件
						bs, err := ioutil.ReadFile(filePath)
						if err != nil {
							log.Println(err)
							return
						}
						for i := range location.TransmitAll {
							transmitAll(&request, header, location, i, bs)
						}
					}()
				}

				w.Header().Set("Content-Type", contentType)
				w.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size()))
				_, _ = io.CopyBuffer(w, f, buf)
				return
			} else if len(location.ProxyPass) > 0 {
				proxy := reverseProxies[i]
				if proxy != nil {
					proxy.ServeHTTP(w, r)
					return
				}
			}

		not_found:
			{
				http.NotFound(w, r)
				return
			}
		}

		//没有命中的情况，发送404
		http.NotFound(w, r)
	}
}

func requestFactory(remote *url.URL, reg *regexp.Regexp) func(r *http.Request) {
	//remote := remotes[i]
	//reg := regs[i]
	return func(req *http.Request) {
		req.URL.Host = remote.Host
		req.URL.Path = remote.Path + reg.ReplaceAllString(req.URL.Path, "")
		req.URL.Scheme = remote.Scheme
		req.Host = remote.Host
		//req.Header.Set("Server", remote.Host)
		//req.Header.Set("Host", remote.Host)
		req.Header.Set("X-Forwarded-Host", req.Header.Get("Host"))
	}
}

func responseFactory(location *LocationBlock, remote *url.URL, reg *regexp.Regexp) func(r *http.Response) error {
	return func(response *http.Response) error {
		needToReadBody := 0
		//需要写日志
		contentType := response.Header.Get("Content-Type")
		if len(location.LogDay) > 0 {
			if len(contentType) > 0 {
				for i := range location.LogFilter {
					if strings.Index(contentType, location.LogFilter[i]) > -1 {
						needToReadBody |= RESP_LOG
						break
					}
				}
			} else {
				needToReadBody |= RESP_LOG
			}
		}
		//数据适配器
		if len(location.FormatterParam) > 0 {
			query := response.Request.URL.Query()
			if len(query.Get(location.FormatterParam)) > 0 {
				needToReadBody |= RESP_FORMAT
			}
		}

		//数据转发
		needToReadBody |= withTransmitParam(location, contentType)
		//if len(location.TransmitFilter) > 0 {
		//	for i := range location.TransmitFilter {
		//		if strings.Index(contentType, location.Transmit[i]) > -1 {
		//			if len(location.Transmit) > 0 {
		//				needToReadBody |= RESP_TRANSMIT
		//			}
		//			if len(location.TransmitAll) > 0 {
		//				needToReadBody |= RESP_TRANSMIT_ALL
		//			}
		//			break
		//		}
		//	}
		//} else {
		//	if len(location.Transmit) > 0 {
		//		needToReadBody |= RESP_TRANSMIT
		//	}
		//	if len(location.TransmitAll) > 0 {
		//		needToReadBody |= RESP_TRANSMIT_ALL
		//	}
		//}

		if needToReadBody != 0 {
			body, err := readBody(response)
			if err != nil {
				return nil
			}
			return restoreBody(response, location, body, needToReadBody)
		}

		return nil
	}
}

func withTransmitParam(location *LocationBlock, contentType string) int {
	ret := 0x00
	if len(location.TransmitFilter) > 0 {
		for i := range location.TransmitFilter {
			if strings.Index(contentType, location.TransmitFilter[i]) > -1 {
				if len(location.Transmit) > 0 {
					ret |= RESP_TRANSMIT
				}
				if len(location.TransmitAll) > 0 {
					ret |= RESP_TRANSMIT_ALL
				}
				break
			}
		}
	} else {
		if len(location.Transmit) > 0 {
			ret |= RESP_TRANSMIT
		}
		if len(location.TransmitAll) > 0 {
			ret |= RESP_TRANSMIT_ALL
		}
	}

	return ret
}

//func  ServeHTTP(w http.ResponseWriter, r *http.Request) {
//
//}

//func  Send404(w http.ResponseWriter) {
//	w.WriteHeader(404)
//	_, _ = w.Write([]byte("can not find page"))
//}

/**
代理sthml
*/
func serveShtml(w http.ResponseWriter, base string, path string) error {
	content, err := renderShtml(base, path)
	if err != nil {
		return err
	}
	fileType := http.DetectContentType(content)
	//w.WriteHeader(200)
	header := w.Header()
	length := strconv.Itoa(len(content))
	header.Set("Content-Type", fileType)
	header.Set("Content-Length", length)
	_, err = w.Write(content)
	if err != nil {
		return err
	}
	return nil
}

/**
渲染shtml页面
*/
func renderShtml(base string, path string) ([]byte, error) {
	content, err := ioutil.ReadFile(base + path)
	if err != nil {
		return []byte{}, err
	}
	content = shtmlReg.ReplaceAllFunc(content, func(bs []byte) []byte {
		matched := shtmlReg.FindSubmatch(bs)
		if len(matched) == 0 || len(matched[1]) == 0 {
			return []byte{}
		}
		var p string
		if matched[1][0] == '/' {
			//绝对路径
			p = string(matched[1][1:])
		} else {
			//相对路径
			p = filepath.Dir(base + path)
			base = p + "/"
			p = string(matched[1])
			//p = p + "/" + string(matched[1])
		}
		cnt, err := renderShtml(base, p)
		if err != nil {
			return []byte{}
		}
		return cnt
		//fmt.Println(matched)
		//return []byte("123")
	})
	return content, nil
}

func readBody(response *http.Response) ([]byte, error) {
	b, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}
	err = response.Body.Close()
	if err != nil {
		return nil, err
	}
	return b, err
}

func restoreBody(response *http.Response, block *LocationBlock, body []byte, order int) error {

	//数据转发服务
	if (order & RESP_TRANSMIT_ALL) != 0 {
		for i := range block.TransmitAll {
			go transmitAll(response.Request, response.Request.Header, block, i, body)
		}

	}

	//如果需要格式化数据
	buf := &bytes.Buffer{}
	//if (order & RESP_FORMAT) != 0 {
	//	formatterName := response.Request.URL.Query().Get(block.FormatterParam)
	//	//优先RPC调用
	//	if GlobalManager.RpcRegister.HasMethod(formatterName) {
	//		bs, err := GlobalManager.RpcRegister.Call(formatterName, body)
	//		if err != nil {
	//			log.Println(err)
	//		} else {
	//			buf.Write(bs)
	//		}
	//	} else if formatter := GlobalProxy.getFormatter(formatterName); formatter != nil {
	//		ret := formatter.Format(body)
	//		_, _ = buf.ReadFrom(ret)
	//	}
	//}

	if buf.Len() == 0 {
		buf.Write(body)
	}

	if (order & RESP_LOG) != 0 {
		go func() {
			writeFileLog(response.Request.RequestURI, buf.Bytes(), block)
		}()
	}
	response.ContentLength = int64(buf.Len())
	response.Body = ioutil.NopCloser(bytes.NewReader(buf.Bytes()))
	response.Header.Set("Content-Length", strconv.Itoa(buf.Len()))

	return nil
}

func transmitAll(request *http.Request, header http.Header, location *LocationBlock, idex int, body []byte) {
	//因为要转发body，必须为post
	req, err := http.NewRequest("POST", location.TransmitAll[idex], nil)
	if err != nil {
		log.Println(err)
		return
	}
	header.Set("Proxy-URL", request.URL.String())
	req.Header = header

	//重试
	retry := make([]uint16, len(location.TransmitInterval)+1)
	retry[0] = 0
	copy(retry[1:], location.TransmitInterval)
transmit:
	for _, v := range retry {
		select {
		case <-time.After(time.Duration(v) * time.Second):
			var bs []byte
			var err error
			req.Body = ioutil.NopCloser(bytes.NewReader(body))
			resp, err := httpClient.Do(req)
			if err != nil {
				continue transmit
			}
			bs, err = ioutil.ReadAll(resp.Body)
			if err != nil {
				goto close
			}
			//请求成功
			if bytes.Equal(bs, []byte("OK")) {
				resp.Body.Close()
				break transmit
			}
		close:
			{
				resp.Body.Close()
			}
		}
	}
}

//func  writeLog() error {
//
//	//log.Println(response.Header.Get("Content-Type"))
//	//还要写回去
//	body := ioutil.NopCloser(bytes.NewReader(b))
//	response.Body = body
//	response.ContentLength = int64(len(b))
//	response.Header.Set("Content-Length", strconv.Itoa(len(b)))
//
//	go writeFileLog(response.Request.RequestURI, b, block)
//
//	return nil
//}

func writeFileLog(url string, bs []byte, block *LocationBlock) {
	targetFile := block.LogDay + "/" + time.Now().Format("2006-01-02") + ".txt"
	var buf bytes.Buffer
	buf.WriteString(time.Now().Format("2006-01-02 15:04:05"))
	buf.WriteByte('\n')
	buf.WriteString((url))
	buf.WriteByte('\n')
	buf.Write(bs)
	buf.WriteByte('\n')
	buf.WriteString("================")
	buf.WriteByte('\n')

	httpLogMutex.Lock()
	defer httpLogMutex.Unlock()
	f, _ := os.OpenFile(targetFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
	defer f.Close()
	bbuf := pool.Get(1024)
	_, _ = io.CopyBuffer(f, &buf, bbuf)
}
